//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <bulverde.h>
#include <drvpub.h>
#include "audiocard.h"
#include "ac97codec.h"
#include "ac97link.h"

#define PAC_REG_BASE 0x80500200   /* Primary Audio Codec */
#define SAC_REG_BASE    0x80500300  /* Secondary Audio Codec */

static DzMutex   s_lock;

// Try to lock the AC Link for command/status accesses to a codec.
ECode Ac97LinkLock()
{
    ECode status = NOERROR;
    UInt32 carTmp;

    carTmp = CAR;
    if (carTmp & XLLP_AC97_CAR_CAIP_MSK) // "1" in CAIP bit means lock failed.
    {
        status = E_ACCESS_DENIED;
    }
    return (status);
}

void  Ac97Lock()
{
    WaitResult wr;
    return s_lock.Lock(&wr);
}

void  Ac97Unlock()
{
    return s_lock.Unlock();
}

ECode  RawAc97Read(UInt16 offset,
                UInt16 *pdata,
                UInt32 maxRWTimeOutUs,
                XLLP_AC97_CODEC_SEL_T codecSel)
{
    ECode status = NOERROR;
    ECode gotLink;
    UInt32 timeRemaining;
    volatile UInt32* pCodecReg;

    if (XLLP_AC97_CODEC_PRIMARY == codecSel) {
        pCodecReg = (UInt32*)PAC_REG_BASE;
    }
    else {
        pCodecReg = (UInt32*)SAC_REG_BASE;
    }
    pCodecReg += (offset>>1);

    //Lock the ACLINK
    timeRemaining = XLLP_AC97_LOCK_TIMEOUT_DEF;
    do {
        gotLink = Ac97LinkLock();
        if (FAILED(gotLink)) // 1 usec is a long time.  Skip delay if possible.
        {
            DzDelay(100);    //
        }
    } // Wait while time remaining and ACLINK not available
    while (timeRemaining--&& (FAILED(gotLink)));

    if (FAILED(gotLink)) // Didn't get the ACLINK
    {
        status = E_ACCESS_DENIED;
    }
    else // We got the link. Perform the write operation and don't wait.
    {
         // First, clear old read status indications.
        GSR = XLLP_AC97_GSR_SDONE_MSK | XLLP_AC97_GSR_RCS_ERR_MSK;
        //may error
        // This is THE DUMMY READ.
        *pdata = (UInt16)(*pCodecReg);

        // Wait for read I/O with codec to complete before doing real read.
        timeRemaining = maxRWTimeOutUs;
        do {
            DzDelay(100);
        } // Wait while time remaining and read I/O still incomplete
        while ((timeRemaining--) && (!(GSR & XLLP_AC97_GSR_SDONE_MSK)));

        if ((GSR & XLLP_AC97_GSR_SDONE_MSK)
            && (!(GSR & XLLP_AC97_GSR_RCS_ERR_MSK))) {
             // succeed in reading. clear status bits first.
            GSR = XLLP_AC97_GSR_SDONE_MSK | XLLP_AC97_GSR_RCS_ERR_MSK;
            *pdata = (UInt16)(*pCodecReg); // THE REAL READ.

            timeRemaining = maxRWTimeOutUs;
            do {
                DzDelay(100);
            } // Wait while time remaining and read I/O still incomplete
            while ((timeRemaining--) && (!(GSR & XLLP_AC97_GSR_SDONE_MSK)));
        }
        else // failed
        {
            status = E_ACCESS_DENIED;
            CAR = XLLP_AC97_CAR_CAIP_CLEAR;
        } // else  (OK to do real read)
    } // else  (We got the link.  Perform the read operations.)

    return (status);
}

ECode  RawAc97Write(UInt16 offset,
                UInt16 data,
                UInt32 maxRWTimeOutUs,
                XLLP_AC97_CODEC_SEL_T codecSel)
{
    ECode status = NOERROR;
    ECode gotLink;
    UInt32 timeRemaining;
    UInt32* pCodecReg;

    if (XLLP_AC97_CODEC_PRIMARY == codecSel) {
        pCodecReg = (UInt32*)PAC_REG_BASE;
    }
    else {
        pCodecReg = (UInt32*)SAC_REG_BASE;
    }
    pCodecReg += (offset>>1);

    //Lock the ACLINK
    timeRemaining = XLLP_AC97_LOCK_TIMEOUT_DEF;
    do {
        gotLink = Ac97LinkLock();
        if (FAILED(gotLink)) // 1 usec is a long time.  Skip delay if possible.
        {
            DzDelay(10*100);
        }
    } // Wait while time remaining and ACLINK not available
    while (timeRemaining--&& (FAILED(gotLink)));

    if (FAILED(gotLink)) // Didn't get the ACLINK
    {
        status = E_ACCESS_DENIED;
    }
    else // We got the link. Perform the write operation and don't wait.
    {
        // First, clear old write status indication CDONE by writing a ONE to that bit.
        GSR = XLLP_AC97_GSR_CDONE_MSK;

        *pCodecReg = (UInt32)data;       // Now the write!
        //*pCodecReg = (XLLP_VUINT32_T)data;
        // Wait until write cycle is complete. There should be a way
        //  to do this speculatively at the beginning of the procedure.
        //  Need to discover it. Too inefficient to always wait.

        if (offset == 0) return status;

        timeRemaining = maxRWTimeOutUs;
        do {
            DzDelay(10*100);
        } // Wait while time remaining and command I/O still incomplete.
        while ((timeRemaining--) && !(GSR & XLLP_AC97_GSR_CDONE_MSK));
        if (!(GSR & XLLP_AC97_GSR_CDONE_MSK)) {
            status = E_ACCESS_DENIED;
        }
    }  // Got AC link

    return(status);
}

ECode Ac97_Read(UInt8 Offset, UInt16 * Data)
{
    ECode retval = NOERROR;

    Ac97Lock();

    retval = RawAc97Read((UInt16)Offset, (UInt16 *)Data,
                                 5000,
                                 XLLP_AC97_CODEC_PRIMARY) ;
    ;

    Ac97Unlock();

    return(retval);
}

ECode  Ac97_Write(UInt8 Offset, UInt16 Data)
{
    ECode retval = NOERROR;

    Ac97Lock();

    retval = RawAc97Write((UInt16) Offset , (UInt16) Data,
                                 5000,
                                 XLLP_AC97_CODEC_PRIMARY);

    Ac97Unlock();

    return(retval);
}

#define GCR_COLD_RST (1 << 1) /* AC'97 Cold Reset (0 = active) */
#define GSR_PCR  (1 << 8) /*  Primary Codec Ready */
void Ac97_Reset(AudioContext *pCxt)
{
    DBGOUT(DBG_LOG, ("Ac97_Reset\n"));

    // ???
    CKEN |= 0x80000000;
    DzDelay(100);
    CKEN &= ~(0x80000000);
    DzDelay(100);

    GCR = 0;
    DzDelay(500);

    // Activate a cold reset
    GCR &= ~GCR_COLD_RST;
    DzDelay(10000);
    GCR |= GCR_COLD_RST;// | GCR_CDONE_IE | GCR_SDONE_IE ;
    DzDelay(1000);
    GCR |= 0x04;
    DzDelay(1000);

    GPSR3 |= 1 << 17;   // GPIO113, workaround to reset the code
    DzDelay(300);

    int retry = 3000;
    while (!(GSR & GSR_PCR) && retry--) {
        DBGOUT(DBG_LOOP, ("@"));
        DzDelay(1000);
        // Sleep
    }

    if (retry <= 0) {
        cprintf("Ac97 control fail, GCR %x, GSR %x\n", GCR, GSR);
    }
}

#define ACLINK_CLOSE(pCxt) \
    (!(pCxt->auxState & (AUXSTATE_ACLINK_AUDIO | AUXSTATE_ACLINK_TOUCH)))
void Ac97_WarmReset(AudioContext *pCxt, UInt32 flag)
{
#if 1
    assert(AUXSTATE_ACLINK_AUDIO == flag || AUXSTATE_ACLINK_TOUCH == flag);

    DBGOUT(DBG_ACLINK,
        ("Ac97_WarmReset, flag = %x, auxState = %x\n", flag, pCxt->auxState));

    WaitResult wr;
    pCxt->acLinkLock.Lock(&wr);

    if (ACLINK_CLOSE(pCxt)) {
        DBGOUT(DBG_ACLINK, ("Ac97_WarmReset, Open ACLINK\n"));
        DBGOUT_FUNC(GSR & GSR_PCR, cprintf("Ac97_WarmReset, Why here?\n"));

        GCR &= ~XLLP_AC97_GCR_LINK_OFF_MSK;
        CKEN &= ~(0x80000000);

        GCR |= 0x04;
        DzDelay(30);

        CKEN |= 0x00000004;

        while (!(GSR & GSR_PCR)) {
            DBGOUT(DBG_LOOP, ("@"));
        }
    }
    pCxt->auxState |= flag;

    pCxt->acLinkLock.Unlock();
#endif
}

void Ac97_Close(AudioContext *pCxt, UInt32 flag)
{
#if 1
    assert(AUXSTATE_ACLINK_AUDIO == flag || AUXSTATE_ACLINK_TOUCH == flag);

    DBGOUT(DBG_ACLINK,
        ("Ac97_Close, flag = %x, auxState = %x\n", flag, pCxt->auxState));

    WaitResult wr;
    pCxt->acLinkLock.Lock(&wr);

    if (ACLINK_CLOSE(pCxt)) goto Exit; // Already closed

    pCxt->auxState &= ~flag;

    if (ACLINK_CLOSE(pCxt)) {
        UInt16 reg;
        DBGOUT(DBG_ACLINK, ("\n\nAc97_Close, Close ACLINK\n"));
        if (GSR & XLLP_AC97_GSR_ACOFFD_MSK) {
            DBGOUT(DBG_ACLINK, ("Ac97_Close, AC Link is already shut down.\n"));
            return;
        }
        Ac97_Write(0x44, 0x0080); // Switch to extern clock
        // Disables PLL
        Ac97_Read(0x3c, &reg);
        reg |= 0x0200;
        Ac97_Write(0x3c, reg);
        // Close ACLINK
        Ac97_Read(0x26, &reg);
        reg |= 0x3000;
        Ac97_Write(0x26, reg);

        GCR |= XLLP_AC97_GCR_LINK_OFF_MSK;
        CKEN |= 0x80000000;
        // Wait the AC-link shutdown is complete
        while (!(GSR & XLLP_AC97_GSR_ACOFFD_MSK));
        CKEN &= ~(0x80000000);
        CKEN &= ~0x00000004;
    }

Exit:
    pCxt->acLinkLock.Unlock();
#endif
}
